package com.ruoyi.common.gscUtil.StudyUtil.redisUtil;


//import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 穿透，redis 没有，mysql 没有；
 * 击穿，redis 没有，mysql 有
 * 击穿紧接着就是雪崩
 *如果缓存集中在一段时间内失效，发生大量的缓存穿透，所有的查询都落在数据库上，造成了缓存雪崩。
 *
 *   redis的《***缓存雪崩****》解决方案
 * 定义： 缓存雪崩是指在某一时刻，大量缓存同时失效，导致所有请求直接打到数据库，造成数据库压力骤增，甚至崩溃
 *互斥锁适用于高并发场景，防止多个线程同时查询数据库。
 * 限流与降级适用于极端情况，保护数据库不被压垮。
 * @author gsc
 * @date 2025/2/18
 */
public class redisSnowSlideUtil {
    /**
     * 实现方案：
     * 1.为什么会穿透：mysql找不到，没有缓存到 redis；
     * 2.为什么会击穿，大都是发生在redis缓存过期自动删除，然后新请求直接去找 mysql；
     * 3.为什么会雪崩，就是穿透redis、击穿mysql 之后就发生雪崩
     */


    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private RedisCache redisCache;

    //1. 设置不同的过期时间
    public void differentExpireTime(){
        // 设置缓存时，添加随机过期时间
        int baseExpireTime = 60 * 60; // 基础过期时间，1小时
        int randomExpireTime = new Random().nextInt(300); // 随机增加0-5分钟的过期时间,避免同时失效
        redisCache.setCacheObject("key","differentExpireTime",baseExpireTime + randomExpireTime,TimeUnit.SECONDS);
    }


    //2.使用缓存预热
    //在缓存失效之前，提前加载缓存数据，避免缓存失效后大量请求直接打到数据库。 提前从数据库拿数据到redis
    //预先去更新缓存，再即将发生大并发访问前手动触发加载缓存不同的key，设置不同的过期时间，让缓存失效的时间点尽量均匀
    public void cacheWarmUp() {
        List<String> dataList = new ArrayList<>(); // 从数据库获取数据
        for (String dt : dataList) {
            // 模拟预热缓存
            redisTemplate.opsForValue().set("key1", "value1", 60, TimeUnit.MINUTES);
            redisTemplate.opsForValue().set("key2", "value2", 60, TimeUnit.MINUTES);
        }
        // 更多缓存项...
    }

    //3.使用互斥锁（Mutex Lock）
    //当缓存失效时，使用互斥锁来确保只有一个线程去加载数据，其他线程等待。
    //这种方案思路比较简单，但是存在一定的隐患，如果在查询数据库 + 和 重建缓存（key失效后进行了大量的计算）时间过长，
    // 也可能会存在死锁和线程池阻塞的风险，高并发情景下吞吐量会大大降低！但是这种方法能够较好地降低后端存储负载，并在一致性上做得比较好
    public Object getMutexLock(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            // 缓存失效，尝试获取锁
            String lockKey = key + ":lock";
            boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS); //Absent:不在
            if (locked) {
                try {
                    // 从数据库加载数据
                    value = loadDataFromDB(key);
                    // 重新设置缓存
                    redisTemplate.opsForValue().set(key, value, 60, TimeUnit.MINUTES);
                } finally {
                    // 释放锁
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未获取到锁，等待一段时间后重试
                try {
                    Thread.sleep(100); //线程休眠
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); //线程中断
                }
                return getMutexLock(key); // 重试
            }
        }
        return value;
    }
    private Object loadDataFromDB(String key) {
        // 模拟从数据库加载数据
        return "data_from_db";
    }

    //4.使用限流和降级
    //在缓存失效时，使用限流和降级策略，避免大量请求直接打到数据库。可以使用Hystrix等工具实现限流和降级。
    // 使用Hystrix实现限流和降级
//    @HystrixCommand(fallbackMethod = "getDataFallback") //转向降级方法 ⬇ ⬇
    public String getData(String key) {
        String value = redisCache.getCacheObject(key);
        if (value == null) { // 缓存失效
            value = loadDataFromDB(key).toString(); // 从数据库加载数据
            redisCache.setCacheObject(key,value,60 * 60,TimeUnit.SECONDS);// 设置缓存
        }
        return value;
    }

    public String getDataFallback(String key) {//降级处理
        return "fallback value"; //  降级逻辑，例如返回默认值、缓存数据或提示信息等
    }




}
